#include"uhci.h"
#include"memrw.h"
#include<dos.h>
#ifdef DEBUG
#include<stdio.h>
#endif

// reference spec:
//                      Universal Host Controller Interface (UHCI)
//                      Universal Serial Bus Specification 2.0

//declaration
//

static  void buildTD (BYTE * TDPtr, DWORD LinkPointer,DWORD  ControlStatus,BYTE PID, BYTE DevAddr, BYTE EndPt, BYTE DataTog, WORD MaxLen,DWORD buffer);
static  void buildQH(BYTE * bufferPtr, DWORD HeadLink, DWORD ElementLink);	
static  void QHTDSchedule(HCStrucType * HCPtr);
static  void insertTDintoQH(BYTE * QHPtr, BYTE * TDPtr);
static  void insertQHintoQH(BYTE * QHA, BYTE * QHB);
static  void resetHC(WORD base);
static  void startHC(WORD base);
static  void stopHC(WORD base);
static  void resetPort(WORD base,BYTE portnum);
static  void enablePort(WORD base,BYTE portnum);
static  int   waitTDDone(BYTE * TDPtr);


ControllerAccessMethodType UHCIAccessMethod={&UHCI_ControllerInitial,\
											&UHCI_ControlTransfer,\
	                                                                      &UHCI_bulkTransfer,\
	                                                                      &UHCI_interruptTransfer,\
	                                                                      &UHCI_pointToDevBuffer,\
	                                                                      &UHCI_DevPortInit};


/******************************************************************************
    Function:
        UHCI_ControllerInitial

    Description:
	initialize UHCI controller.

    Entry:
	pHC   pointer to controller description

    Exit:
        none
******************************************************************************/
void UHCI_ControllerInitial(void * pHC)
{
         //2 get host controller info
	 HCStrucType * CPtr=(HCStrucType *)pHC;
	  CPtr->bHCType= UHCITYPE;	  
	  CPtr->bNumPorts= 2;
	  CPtr->dHcdDataArea= readPortDWORD(CPtr->dBaseAddress + FLBASEADD ) & 0xfffff000;
	  CPtr->pDescriptorPtr=allocAlignMem(sizeof(UHCIDescriptorType) + 16, 16);	  
		
          //2. setup framelist   
	  stopHC(CPtr->dBaseAddress);
          QHTDSchedule( CPtr);			  
	  startHC(CPtr->dBaseAddress);  

	   //2.. init device	 	  
	  for(BYTE i =1; i<=2; i++)//only 2 ports
	  {

	       if(readPortWORD(CPtr->dBaseAddress + PORTBASE + (i-1) *2)   &  CCS) //device exist on port
	       	{
	       	         resetPort(CPtr->dBaseAddress, i);					 
	                       enablePort(CPtr->dBaseAddress, i);	  
                              devInitialize(CPtr,i); //  init here.       		         
	       	}
	       else //port is 
	       	{
                         continue; 
	       	}
	  }


	   return;	   	 
}






/******************************************************************************
    Function:
        UHCI_ControlTransfer

    Description:
	UHCI control tansfer type

    Entry:
	ptr --  device description
	RFPtr -- request data
    Exit:
        status    1---stalled  ; 0 ----ok.
    Note: 
          if want to transmit data , please place it in UHCIDescriptor.buffer1;
          receve data after transation also put it in UHCIDescriptor.buffer1;
******************************************************************************/
BYTE  UHCI_ControlTransfer(DeviceInfoPtr ptr,  RequstFormatType * RFPtr)
{
           TDtype * setupTDPtr;
           TDtype * statusTDPtr;
           TDtype * DataTDPtr;	 
		  
           UHCIDescriptorType  *DPtr=(UHCIDescriptorType  *)ptr->pHCStrucPtr->pDescriptorPtr; // get descriptor QH 
          BYTE buffer[sizeof(TDtype)  * (3 + 1)];
	   setupTDPtr= (TDtype *)alignMem(buffer, 16);
	   statusTDPtr=setupTDPtr +1;
	   DataTDPtr=setupTDPtr +2;      
	   
           //2. build setup TD
          BYTE DeviceAddress = ptr->bDeviceAddress;
	   WORD MaxLength = ptr->wEndp0MaxPacket;
	   BYTE Endpoint = 0;/////ptr->bEndpointNum; control endpoint 
	   DWORD CS= ActiveStatus | C_ERR;
	   if(ptr->bEndpointSpeed==01) CS = CS |LowSpeed;
	   
          buildTD((BYTE *)setupTDPtr,0, CS, SETUPPID, DeviceAddress, Endpoint, 0, sizeof(RequstFormatType)-1, calcLineAddress((BYTE * )RFPtr));
           buildQH((BYTE *)&(DPtr->QH2ms), 0, calcLineAddress((BYTE *)setupTDPtr));
           if(!waitTDDone((BYTE *)setupTDPtr))
	    {
	     #ifdef DEBUG
	          printf("setup not ready\n");
	     #endif	 
           }
	 #ifdef DEBUG
           dump((BYTE *)setupTDPtr, 16);/////
         #endif	
		   	
 
           //2. build DATA TD
           int  DataLengthBegin=RFPtr->wLength;		
           BYTE DataPID;		   
           if(DataLengthBegin)
           {
                     int DataLengthTransferred=0;
		       int DataLength;			 
                     BYTE DataTogg=1;


		     if(DataLengthBegin>=MaxLength) DataLength=MaxLength;			 
		     else   DataLength=	 DataLengthBegin;	
			 	
		     if(RFPtr->bmRequestType  & 0x80)   DataPID=INPID;
		     else 	 DataPID=OUTPID;

		     while(DataLengthBegin>0)
		     {
                              buildTD((BYTE *)DataTDPtr, 0, CS, DataPID, DeviceAddress,Endpoint, DataTogg, DataLength-1, calcLineAddress(DPtr->buffer1) + DataLengthTransferred);		     
                              insertTDintoQH((BYTE *)&(DPtr->QH2ms), (BYTE *)DataTDPtr);						  
                             if(!waitTDDone((BYTE *)DataTDPtr))
				 {
				  #ifdef  DEBUG
				        printf("data phase not ready\n");		
				  #endif
                              }
			 #ifdef  DEBUG				 
                         dump((BYTE *)DataTDPtr, 16);/////					
                      #endif      
			     DataTogg ^=1;

	
			     DWORD tempValue1=DataTDPtr->ControlStatus & 0x7ff;	 			    
		             if(tempValue1!=  DataLength-1)break; // transferred data is short packet. 
		             
			     DataLengthBegin -=	 DataLength;
			     DataLengthTransferred +=	 DataLength;
				 
		             if(DataLengthBegin<MaxLength) DataLength=	 DataLengthBegin;			 	                     					 
		     }

           }
		   
	   //2. build status TD
	     if(RFPtr->bmRequestType  & 0x80)   DataPID=OUTPID;
	     else 	 DataPID=INPID;
	     buildTD((BYTE *)statusTDPtr, 0, CS, DataPID, DeviceAddress, Endpoint, 1, 0x7ff, 0);
             insertTDintoQH((BYTE *)&(DPtr->QH2ms), (BYTE *)statusTDPtr);		 
             if(!waitTDDone((BYTE *)statusTDPtr))
       	{
              #ifdef DEBUG
       	      printf("status phase not ready\n");			 
		#endif	  
             	}

             insertTDintoQH((BYTE *)&(DPtr->QH2ms), 0);				 

            DWORD CStas=statusTDPtr->ControlStatus;// status after request
            BYTE status=0;
            if(CStas & StalledStatus)status =1;//stalled
	     else status =0;		//ok.
  	     return status ;		 
}




/******************************************************************************
    Function:
        UHCI_bulkTransfer

    Description:
	UHCI bulk tansfer type

    Entry:
	DIPtr --  device description
	direction -- data transfer direction
       bufferPtr: data buffer to send or received
       length-- data length
       ToggleValue--refer to usb2.0 8.5.2. For bulk read, buld write, data begins with 0, alter each success transfer.
       TransLength-- actual transfer length, byte unit.
	
    Exit:
        status    0 ----ok.
                    bit0: STALL
                    bit1: time out
                    bit2: error

******************************************************************************/
BYTE UHCI_bulkTransfer(DeviceInfoPtr DIPtr, BYTE direction, BYTE * bufferPtr,WORD length,WORD *TransLength)
{
          BYTE  PID;
	   BYTE  EndPoint;
	   WORD MaxLen;
	   BYTE  USBAddress;
	   BYTE  Toggle;
          BYTE  status=0;
		  
	   if(direction)
	   { 
	          PID= INPID;
		   EndPoint = DIPtr->bBulkInEndpoint;
		   MaxLen=DIPtr->wBulkInMaxPkt;
		   Toggle=DIPtr->bBulkInToggle;
	   }
	   else
	   {
	          PID= OUTPID;
		   EndPoint = DIPtr->bBulkOutEndpoint;
		   MaxLen=DIPtr->wBulkOutMaxPkt;	   
		   Toggle=DIPtr->bBulkOutToggle;
	   }
	   
	   USBAddress=DIPtr->bDeviceAddress;

	   //send TD one by one;               or send all TD one time
	   BYTE Cycles= length / MaxLen;
	   if(length % MaxLen) Cycles++;


	    TDtype *TDPtr;	   
           BYTE  buffer[sizeof(TDtype)*1   +  16];
	    TDPtr=(TDtype *)alignMem(buffer, 16);		

	   DWORD CS= ActiveStatus | C_ERR;
	   if(DIPtr->bEndpointSpeed==01) CS = CS |LowSpeed;	   

	   WORD TfedLength=0;
	   WORD PacketLength;
	   if(Cycles ==1) PacketLength=length;
	   else PacketLength=MaxLen;

	   for(int i=0; i<Cycles; i++)
	   {

                      buildTD((BYTE *)TDPtr,0, CS,  PID,USBAddress, EndPoint, Toggle, PacketLength-1, calcLineAddress(bufferPtr) + TfedLength);
 
             insertTDintoQH((BYTE *)&((UHCIDescriptorType *)DIPtr->pHCStrucPtr->pDescriptorPtr)->QH1ms  , (BYTE *)TDPtr);
                      if(!waitTDDone((BYTE *)TDPtr))
                      {
#ifdef DEBUG
                      	printf("bulk transferred error\n");	
#endif
                      }
				  
#ifdef DEBUG		
                     dump((BYTE *)TDPtr, 16);//////
#endif                     
		      Toggle ^=1;
		      	
		      DWORD tempValue=TDPtr->ControlStatus & 0x7ff;	 		
                     TfedLength +=(WORD)tempValue +1 ;
                     *TransLength= TfedLength;  
					 
		      if(tempValue != PacketLength-1)	break;
			  
		      if(TfedLength + MaxLen > length)PacketLength=length - TfedLength; //last packet
			  
                     *TransLength= TfedLength;  			  
			  
		      if(TDPtr->ControlStatus & (DataBufferError | BabbleDetected | NAKReceived | BitstuffError))
		      	{
		      	      status=OtherError;    
			      break;		  
		      	}
		      if(TDPtr->ControlStatus & StalledStatus)
		      	{
		      	       status=Stalled;
				break;	   
		      	}
			if(TDPtr->ControlStatus & CRCTimeOutError)
			{
			       status=TimeOut;
				break;   
			}					  
	   }


          insertTDintoQH((BYTE *)&((UHCIDescriptorType *)DIPtr->pHCStrucPtr->pDescriptorPtr)->QH1ms  , 0);	          
	   	   if(direction)
	   { 

		 DIPtr->bBulkInToggle=Toggle;
	   }
	   else
	   {

		   DIPtr->bBulkOutToggle=Toggle;
	   }
           return status;
}



/******************************************************************************
    Function:
        UHCI_interruptTransfer

    Description:
	UHCI bulk tansfer type

    Entry:
	DIPtr --  device description
	direction -- data transfer direction
       bufferPtr: data buffer to send or received
       length-- data length    
       TransLength-- actual transfer length, byte unit.
	
    Exit:
        status    0 ----ok.
                    bit0: STALL
                    bit1: time out
                    bit2: error

******************************************************************************/
BYTE UHCI_interruptTransfer(DeviceInfoPtr DIPtr, BYTE direction, BYTE * bufferPtr,WORD length,WORD *TransLength)
{
          BYTE  PID;
	   BYTE  EndPoint;	   
	   BYTE  USBAddress;
	   BYTE  Toggle;
          BYTE  status=0;
		  
	   if(direction)
	   { 
	          PID= INPID;
		   EndPoint = DIPtr->bIntEndpoint;		   
		   Toggle=DIPtr->bIntEndPointToggle;
	   }
	   else
	   {
	          //PID= OUTPID;
		   //EndPoint = DIPtr->bBulkOutEndpoint;
		   //MaxLen=DIPtr->wBulkOutMaxPkt;	   
		   //Toggle=DIPtr->bBulkOutToggle;
		   
		   //we have not define interrupt out endpoint item in device info
	   }
	   
	   USBAddress=DIPtr->bDeviceAddress;  

	    TDtype *TDPtr;	   
           BYTE  buffer[sizeof(TDtype)*1   +  16];
	    TDPtr=(TDtype *)alignMem(buffer, 16);		

	   DWORD CS= ActiveStatus | C_ERR |IOC;
	   if(DIPtr->bEndpointSpeed==01) CS = CS |LowSpeed;	   

	   WORD PacketLength;
	   PacketLength=length;


                      buildTD((BYTE *)TDPtr,0, CS,  PID,USBAddress, EndPoint, Toggle, PacketLength-1, calcLineAddress(bufferPtr) );

                  insertTDintoQH((BYTE *)&((UHCIDescriptorType *)DIPtr->pHCStrucPtr->pDescriptorPtr)->QH1ms  , (BYTE *)TDPtr);
                      if(!waitTDDone((BYTE *)TDPtr))
                      {
#ifdef DEBUG
                      	printf("bulk transferred error\n");	
#endif
                      }
				  
#ifdef DEBUG		
                     dump((BYTE *)TDPtr, 16);//////
#endif                     
		      Toggle ^=1;
		      	
		      DWORD tempValue=TDPtr->ControlStatus & 0x7ff;	 	  
                     *TransLength= (WORD)tempValue +1;  		  
			  
		      if(TDPtr->ControlStatus & (DataBufferError | BabbleDetected | NAKReceived | BitstuffError))
		      	{
		      	      status=OtherError;    
			
		      	}
		      if(TDPtr->ControlStatus & StalledStatus)
		      	{
		      	       status=Stalled;
			
		      	}
			if(TDPtr->ControlStatus & CRCTimeOutError)
			{
			       status=TimeOut;
			 
			}

          
	   	   if(direction)
	  	 { 

		 DIPtr->bIntEndPointToggle=Toggle;
	  	 }
	   	else
	  	 {

		   //DIPtr->bIntEndPointToggle=Toggle;
	   	 }
           return status;
}



/******************************************************************************
    Function:
        UHCI_pointToDevBuffer

    Description:
	UHCI device data buffer pointer. Control transation , bulk transation data buffer.

    Entry:
	device info pointer

    Exit:
       UHCI device data buffer pointer
******************************************************************************/
BYTE * UHCI_pointToDevBuffer(DeviceInfoPtr ptr)
{
		return	((UHCIDescriptorType *)ptr->pHCStrucPtr->pDescriptorPtr)->buffer1;
}

/******************************************************************************
    Function:
        UHCI_DevPortInit

    Description:
	UHCI port init.

    Entry:
	device info pointer

    Exit:
       none
******************************************************************************/
void UHCI_DevPortInit(DeviceInfoPtr ptr,BYTE portIndex)
{
    	if(readPortWORD(ptr->pHCStrucPtr->dBaseAddress+ PORTBASE + (portIndex -1)*2 ) & LSDA) 
			ptr->bEndpointSpeed=Low_Speed_Device ; // 01 =low speed device.	
  	else  	ptr->bEndpointSpeed=Full_Speed_Device; // 2 = full speed		
}



/******************************************************************************
    Function:
        QHTDSchedule

    Description:
	construction QT,TD 

    Entry:
	HCPtr   host controller description

    Exit:
        none
******************************************************************************/
void QHTDSchedule(HCStrucType * HCPtr)
{

          DWORD pHCDAREA= HCPtr->dHcdDataArea;
          int i=0;	   	   

         UHCIDescriptorType  *DPtr=(UHCIDescriptorType  *)HCPtr->pDescriptorPtr; // get descriptor QH 
         
           DWORD * tempPtr=(DWORD *)DPtr;

     //3set QH invalid accord to UHCIDescriptor definition.
      for(i=0;i<12;i++)
      {
             tempPtr[i]=TerminateFlag |QHFlag;

      }
	
      //3 clear frame list
      for(i=0;i<1024;i++)
      {
             writeMemDWORD(pHCDAREA + i *4, TerminateFlag);
      }	  


      //3 link 8ms qh 	   
      for(i=0;i<1024;i=i+8)
      {
            writeMemDWORD(pHCDAREA + i *4, calcLineAddress((BYTE *)&DPtr->QH8ms) | QHFlag);
      }

      //3 link 2ms qh 	   
      for(i=1;i<1024;i=i+2)
      {

            writeMemDWORD(pHCDAREA + i *4, calcLineAddress((BYTE *)&DPtr->QH2ms) | QHFlag);
      }	  

      //3 link 1ms qh
      for(i=0;i<1024;i++)
      {
             //if(FramePtr[i]==TerminateFlag ) 
             if(getMemDWORD(pHCDAREA + i *4) == TerminateFlag ) 
	     {
	           //FramePtr[i]=calcLineAddress((BYTE *)&DPtr->QH1ms) | QHFlag;	
	            writeMemDWORD(pHCDAREA + i *4, calcLineAddress((BYTE *)&DPtr->QH1ms) | QHFlag);
	     }
	     else 
	     {
	             insertQHintoQH((BYTE *)&DPtr->QH8ms, (BYTE *)&DPtr->QH1ms);
	             insertQHintoQH((BYTE *)&DPtr->QH2ms, (BYTE *)&DPtr->QH1ms);		   		 
	     }
      }

	 	
      return;	  
}



/*
void UHCI_activePollingTD(DeviceInfoPtr DIP)
{

        TDtype *TDPtr=(TDtype *)allocAlignMem(sizeof(TDtype)*2, 16);

	 buildTD(TDPtr,0, DWORD ControlStatus, INPID, DIP->bDeviceAddress, DIP->bIntEndpoint,\
	 	BYTE DataTog, 7, DWORD buffer);

}
	
*/

// build TD
// PID   --8 bit
// Device Address  -- 7 bit
// EndPt       -- 4 bit
// MaxLen   --11 bit
void  buildTD (BYTE * TDPtr, DWORD LinkPointer,DWORD  ControlStatus,BYTE PID, BYTE DevAddr, BYTE EndPt, BYTE DataTog, WORD MaxLen,DWORD buffer)
{
      TDtype TDbuffer={0};
      DWORD temp =0;	  
      if(LinkPointer) TDbuffer.LinkPointer =  LinkPointer	  | DepthFirstFlag ;
      else 	  TDbuffer.LinkPointer = TerminateFlag;


     TDbuffer.ControlStatus = ControlStatus;

      temp =0;
      temp=MaxLen;
      temp <<=6;
      temp +=	EndPt;
      temp <<=7;
      temp += DevAddr;
      temp <<=8;
      temp +=  PID;
      if (DataTog) temp ^=DataToggle;
      TDbuffer.token = temp;

      TDbuffer.BufferPoint = buffer;

      memCopy(TDPtr,&TDbuffer,sizeof(TDtype));
      return;	
}




// build QH
// all paras are  16 byte align. invoker should sure for it.
void buildQH(BYTE * bufferPtr, DWORD HeadLink, DWORD ElementLink)
{
        QHtype QHbuffer={0};
        QHbuffer.HeadLink=HeadLink;
	QHbuffer.ElementLink=ElementLink;
	
        //assume HeadLink is QH.
	if(HeadLink)  QHbuffer.HeadLink |= QHFlag;
	else QHbuffer.HeadLink |=TerminateFlag;

        //assume ElementLink is TD.
	if(ElementLink==0) QHbuffer.ElementLink |=TerminateFlag;
	memCopy(bufferPtr, &QHbuffer, sizeof(QHtype));
	return;		
}

//link TD to QH.ElementLink
void insertTDintoQH(BYTE * QHPtr, BYTE * TDPtr)
{      
         if(TDPtr == 0) 
         	{
         	  QHPtr[4]=TerminateFlag;
		  QHPtr[5]=0;
		  QHPtr[6]=0;
		  QHPtr[7]=0;
         	}
	  else 
	  	{
                 DWORD temp= calcLineAddress(TDPtr);
                 QHPtr[4] = temp;
                 QHPtr[5] = temp>>8;		 
                 QHPtr[6] = temp>>16;
                 QHPtr[7] = temp>>24;		 
	  	}
         return;
}

//QHA point to QHB
//link QHB to QHA.HeadLink.
void insertQHintoQH(BYTE * QHA, BYTE * QHB)
{
         DWORD temp=calcLineAddress(QHB);
         QHA[0] = temp | QHFlag;
         QHA[1] = temp>>8;		 
         QHA[2] = temp>>16;
         QHA[3] = temp>>24;		   
       	  return; 
}


int waitTDDone(BYTE * TDPtr)
{
        int  i =0;
	int  WaitTime=5000;	
	for(;i<WaitTime;i++)
	{
	      if(TDPtr[4+2] & 0x80)delay(1); 	
	      else break;	  
	        
	}
	if(i>=WaitTime)return 0;  //error.
	else return 1;
}


void resetHC(WORD base)
{
        writePortWORD(base + USBCMD,HCRESET | GRESET );
	delay(100);	 //spec require
        writePortWORD(base + USBCMD,0 );	
		
        return;
}


void stopHC(WORD base)
{
        writePortWORD(base + USBCMD,0x00 );		
	return;	
}
void startHC(WORD base)
{
        writePortWORD(base + USBCMD,0xc1 );		
	return;	
}
void resetPort(WORD base,BYTE portnum)
{

        WORD portIndex;
	if(portnum == 1) portIndex = PORT1;
	else portIndex=PORT2 ;	
        
        writePortWORD(base+ portIndex,   CCS | CSC | PED | PEDC | PR  );		
        delay(100);


        return;

}
void enablePort(WORD base,BYTE portnum)
{

        WORD portIndex;
	if(portnum == 1) portIndex = PORT1;
	else portIndex=PORT2 ;	


        writePortWORD(base+ portIndex, CCS | CSC | PED | PEDC);
	delay(1);	

	writePortWORD(base+ portIndex, CCS | CSC | PED | PEDC);  // twice ?????
	delay(200);	
	
        return;
}

/******************************************************************************
    Function:
        getUHCIMethod

    Description:
	return UHCI interface.

    Entry:
	none

    Exit:
        return UHCI method interface 
******************************************************************************/
ControllerAccessMethodType * getUHCIMethod()
{
	return &UHCIAccessMethod;
}

